package com.xiaoshuidi.cloud.framework.pay.core.client.impl.yinlian.miniapp;

import cn.hutool.core.date.DateUtil;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.xiaoshuidi.cloud.framework.common.exception.ServiceException;
import com.xiaoshuidi.cloud.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.xiaoshuidi.cloud.framework.common.util.json.JsonUtils;
import com.xiaoshuidi.cloud.framework.pay.core.client.AbstractPayCodeMapping;
import com.xiaoshuidi.cloud.framework.pay.core.client.PayCommonResult;
import com.xiaoshuidi.cloud.framework.pay.core.client.dto.*;
import com.xiaoshuidi.cloud.framework.pay.core.client.impl.yinlian.AbstractYinlianPayClient;
import com.xiaoshuidi.cloud.framework.pay.core.client.impl.yinlian.YinlianClientConfig;
import com.xiaoshuidi.cloud.framework.pay.core.enums.PayFrameworkErrorCodeConstants;
import com.xiaoshuidi.cloud.framework.pay.core.enums.PayStatusEnum;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.net.URLDecoder;
import java.util.Date;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

/**
 * 银联小程序支付接口抽象类
 *
 * @author lee
 */
public abstract class AbstractYinlianMiniAppPayClient extends AbstractYinlianPayClient {
    public AbstractYinlianMiniAppPayClient(Long channelId, String channelCode, YinlianClientConfig config, AbstractPayCodeMapping codeMapping) {
        super(channelId, channelCode, config, codeMapping);
    }


    @Override
    public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws Exception {
        if (StringUtils.hasText(data.getBody())) {
            String[] pairs = StringUtils.tokenizeToStringArray(data.getBody(), "&");
            Map<String, String> form = new TreeMap<>();
            for (String pair : pairs) {
                int idx = pair.indexOf('=');
                if (idx == -1) {
                    form.put(URLDecoder.decode(pair, "UTF-8"), null);
                } else {
                    String name = URLDecoder.decode(pair.substring(0, idx), "UTF-8");
                    String value = URLDecoder.decode(pair.substring(idx + 1), "UTF-8");
                    form.put(name, value);
                }
            }
            if (StringUtils.hasText(config.getSignKey())) {
                String signStr = new TreeMap<>(form).entrySet().stream().filter(e -> StringUtils.hasText(e.getValue())
                                && !"sign".equals(e.getKey()))
                        .map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&"));
                signStr += config.getSignKey();
                String sign = DigestUtils.sha256Hex(signStr);
                if (sign.equalsIgnoreCase(form.get("sign"))) {
                    return new PayOrderNotifyRespDTO().setSuccess(false)
                            .setNotifyStatus("FAILED");
                }
            }
            return new PayOrderNotifyRespDTO()
                    .setMerchantOrderId(form.get("merOrderId"))
                    .setChannelOrderNo(form.get("sqlId"))
                    .setChannelUserId(form.get("buyerId"))
                    .setData(data.getBody())
                    .setPayAmount(Long.valueOf(form.get("totalAmount")))
                    .setSuccessTime(DateUtil.parseLocalDateTime(form.get("payTime")))
                    .setTradeStatus(form.get("status"))
                    .setSuccess(true)
                    .setNotifyStatus("SUCCESS");
        }
        return new PayOrderNotifyRespDTO().setSuccess(false)
                .setNotifyStatus("FAILED");
    }

    @Override
    public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
        return null;
    }

    @SuppressWarnings("unchecked")
    @Override
    public PayCommonResult<PayQueryUnifiedRespDTO<ObjectNode>> unifiedQuery(PayQueryUnifiedReqDTO reqDTO) {
        ObjectNode json = baseParams(config.getMerchantCode(), config.getTerminalCode());
        json.put("merOrderId", reqDTO.getMerchantOrderId());
        json.put("instMid", "QRPAYDEFAULT");
        return call("/v1/netpay/query", json, (code, resp) -> {
                    PayQueryUnifiedRespDTO<ObjectNode> r = new PayQueryUnifiedRespDTO<ObjectNode>()
                            .setRawResponse(resp)
                            .setMerchantOrderId(reqDTO.getMerchantOrderId());
                    if (GlobalErrorCodeConstants.SUCCESS.isEqualTo(code)) {
                        String tradeStatus = resp.get("status").asText();
                        switch (tradeStatus) {
                            case "TRADE_SUCCESS":
                                r.setStatus(PayStatusEnum.SUCCESS)
                                        .setTransactionId(resp.get("targetOrderId").asText())
                                        .setPayTime(DateUtil.parseLocalDateTime(resp.get("payTime").asText()))
                                        .setPayAmount(BigDecimal.valueOf(resp.get("totalAmount").asDouble() / 100D));
                                return r;
                            case "TRADE_CLOSED":
                                r.setStatus(PayStatusEnum.CLOSED);
                                break;
                            default:
                                r.setStatus(PayStatusEnum.WAITING);
                        }
                    }
                    return r;
                }
        );
    }

    @Override
    protected ObjectNode unifiedRefundParams(PayRefundUnifiedReqDTO reqDTO) {
        ObjectNode json = baseParams(config.getMerchantCode(), config.getTerminalCode());
        json.put("merOrderId", reqDTO.getPayTradeNo());
        json.put("instMid", "YUEDANDEFAULT");
        json.put("refundAmount", reqDTO.getAmount());
        json.put("refundOrderId", nextOrderNo());
        return json;
    }

    @Override
    protected PayCommonResult<?> doUnifiedClose(PayCloseUnifiedReqDTO reqDTO) throws Throwable {
        ObjectNode body = JsonUtils.getObjectMapper().createObjectNode();
        body.put("requestTimestamp", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
        body.put("merOrderId", reqDTO.getMerchantOrderId());
        body.put("mid", config.getMerchantCode());
        body.put("tid", config.getTerminalCode());
        body.put("instMid", "MINIDEFAULT");
        return call("/v1/netpay/close", body, (success, resp) -> success);
    }
}
